﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace SimpleMapper
{
    /// <summary>
    /// 映射类型对象
    /// </summary>
    public sealed partial class Mapper
    {
        /// <summary>
        /// 将<typeparamref name="TSource"/>映射为<typeparamref name="TDestination"/>
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TDestination"></typeparam>
        /// <param name="source"></param>
        /// <returns></returns>
        public TDestination Map<TSource, TDestination>(TSource source)
            where TSource : class
            where TDestination : class
        {
            if (source == null) return default;
            var func = GetConvertFunc<TSource, TDestination>();
            var result = func(source);
            return result;
        }

        /// <summary>
        /// 将<typeparamref name="TSource"/>集合映射为<typeparamref name="TDestination"/>列表
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TDestination"></typeparam>
        /// <param name="sources"></param>
        /// <returns></returns>
        public List<TDestination> Map<TSource, TDestination>(IEnumerable<TSource> sources)
            where TSource : class
            where TDestination : class
        {
            if (sources?.FirstOrDefault() == null) return new List<TDestination>();
            var result = new List<TDestination>();
            var func = GetConvertFunc<TSource, TDestination>();
            foreach (var source in sources)
            {
                var item = func(source);
                result.Add(item);
            }
            return result;
        }
        /// <summary>
        /// 将<see cref="object"/>映射为<typeparamref name="TDestination"/>
        /// </summary>
        /// <typeparam name="TDestination"></typeparam>
        /// <param name="source"></param>
        /// <returns></returns>
        public TDestination To<TDestination>(object source)
            where TDestination : class
        {
            if (source == null) return default;
            var sourceRealType = source.GetType();
            var func = GetConvertFunc<object, TDestination>(sourceRealType);
            var result = func(source);
            return result;
        }
        /// <summary>
        /// 将<see cref="object"/>集合映射为<typeparamref name="TDestination"/>列表
        /// </summary>
        /// <typeparam name="TDestination"></typeparam>
        /// <param name="sources"></param>
        /// <returns></returns>
        public List<TDestination> To<TDestination>(IEnumerable<object> sources)
            where TDestination : class
        {
            if (sources?.FirstOrDefault() == null) return new List<TDestination>();
            var result = new List<TDestination>();
            var sourceRealType = sources.First().GetType();
            var func = GetConvertFunc<object, TDestination>(sourceRealType);
            foreach (var source in sources)
            {
                var item = func(source);
                result.Add(item);
            }
            return result;
        }
        /// <summary>
        /// 获取转换方法
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TDestination"></typeparam>
        /// <param name="sourceRealType"></param>
        /// <returns></returns>
        private static Func<TSource, TDestination> GetConvertFunc<TSource, TDestination>(Type sourceRealType = null)
            where TSource : class
            where TDestination : class
        {
            if (sourceRealType != null && sourceRealType.IsValueType) throw new Exception("映射源类型为值类型");
            return GetChangeTypeMethod<TSource, TDestination, Func<TSource, TDestination>>(key => BuildTypeMapFunc<TSource, TDestination>(sourceRealType), null, sourceRealType);
        }
        /// <summary>
        /// 生成类型映射方法
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <typeparam name="TDestination"></typeparam>
        /// <param name="sourceRealType"></param>
        /// <returns></returns>
        private static Func<TSource, TDestination> BuildTypeMapFunc<TSource, TDestination>(Type sourceRealType = null)
        {
            // 源类型
            var srcType = typeof(TSource);
            // 目标类型
            var dstType = typeof(TDestination);

            // 形参
            var source = Expression.Parameter(srcType, "source");
            // 返回标签
            var returnLabel = Expression.Label(dstType);
            // 返回目标对象
            var destination = Expression.Variable(dstType, "destination");
            // 返回标签默认返回对象
            var defaultReturn = Expression.Label(returnLabel, destination);
            // 返回标签显式返回对象
            var destinationReturn = Expression.Return(returnLabel, destination);

            // 返回目标对象赋值
            var destinationAssign = Expression.Assign(destination, Expression.New(dstType));
            // 检查形参是否为null
            var nullCheck = Expression.IfThen(Expression.Equal(source, Expression.Constant(null)), destinationReturn);

            // 源属性集合
            PropertyInfo[] srcProps = null;
            // 目标属性集合
            var dstProps = GetProperties(dstType, false);
            BinaryExpression realSourceAssign = null;
            ParameterExpression realSource = null;
            if (sourceRealType == null)
            {
                srcProps = GetProperties(srcType, false);
                realSource = Expression.Variable(srcType, "realSource");
                realSourceAssign = Expression.Assign(realSource, source);
            }
            else
            {
                srcProps = GetProperties(sourceRealType, false);
                realSource = Expression.Variable(sourceRealType, "realSource");
                realSourceAssign = Expression.Assign(realSource, Expression.Convert(source, sourceRealType));
            }

            // 方法块
            var blockExpressionList = new List<Expression>(dstProps.Length + 4)
            {
                realSourceAssign,
                destinationAssign,
                nullCheck
            };

            // 匹配属性集合 可以自定义属性匹配规则在此处
            var props = srcProps.Join(dstProps, src => GetPropertyMapName(src), dst => GetPropertyMapName(dst),
                        (src, dst) => new { Source = src, Destination = dst }, StringComparer.OrdinalIgnoreCase);
            // 遍历名称匹配的属性集合
            foreach (var prop in props)
            {
                // 源属性 或 目标属性为不可转换类型，则跳过
                if (!prop.Destination.PropertyType.IsConvertibleType() ||
                    !prop.Source.PropertyType.IsConvertibleType())
                    continue;

                // 源属性
                var srcProperty = Expression.Property(realSource, prop.Source);
                // 目标属性
                var dstProperty = Expression.Property(destination, prop.Destination);
                // 源属性类型
                var dstPropertyType = prop.Destination.PropertyType;
                // 目标属性类型
                var srcPropertyType = prop.Source.PropertyType;

                // 赋值表达式
                BinaryExpression propertyAssign = null;
                // 如果两者类型相同
                if (dstPropertyType == srcPropertyType)
                {
                    // 则直接赋值
                    propertyAssign = Expression.Assign(dstProperty, srcProperty);
                }
                else
                {
                    // 获取目标类型的转换方法
                    var converter = ObjectConverter.GetChangeTypeMethodInfo(dstPropertyType);
                    // 转换类型
                    var srcValue = Expression.Call(converter, srcProperty);
                    // 将转换的值赋给目标属性
                    propertyAssign = Expression.Assign(dstProperty, srcValue);
                }
                // 当前属性赋值语句添加到方法块
                blockExpressionList.Add(propertyAssign);
            }
            // 默认返回
            blockExpressionList.Add(defaultReturn);
            // 创建一个方法块
            var methodBlock = Expression.Block(new[] { realSource, destination }, blockExpressionList);
            // 生成lambda表达式
            var lambda = Expression.Lambda<Func<TSource, TDestination>>(methodBlock, source);
            // 编译为委托
            var func = lambda.Compile();
            return func;
        }
    }
}
